package com.sneakxy.cloudbase.platform.controller;

import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.LockedAccountException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.annotation.RequiresAuthentication;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import com.sneakxy.cloudbase.platform.bean.Account;
import com.sneakxy.cloudbase.platform.bean.Launcher;
import com.sneakxy.cloudbase.platform.bean.LauncherSetting;
import com.sneakxy.cloudbase.platform.bean.LauncherSettingId;
import com.sneakxy.cloudbase.platform.bean.dto.AccountDTO;
import com.sneakxy.cloudbase.platform.bean.dto.DictDTO;
import com.sneakxy.cloudbase.platform.bean.dto.DictParams;
import com.sneakxy.cloudbase.platform.bean.dto.InitDTO;
import com.sneakxy.cloudbase.platform.bean.dto.LauncherDTO;
import com.sneakxy.cloudbase.platform.bean.dto.MenuDTO;
import com.sneakxy.cloudbase.platform.service.IAccountService;
import com.sneakxy.cloudbase.platform.service.IDictService;
import com.sneakxy.cloudbase.platform.service.ILauncherService;
import com.sneakxy.cloudbase.platform.service.ILauncherSettingService;
import com.sneakxy.cloudbase.platform.service.IMenuService;
import com.sneakxy.cloudbase.platform.service.ISecurityService;
import com.sneakxy.cloudbase.platform.service.ISettingService;
import com.sneakxy.cloudbase.platform.shiro.UsernameCodeToken;
import com.sneakxy.cloudbase.platform.utils.Constants;
import com.sneakxy.cloudbase.platform.utils.web.Response;

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import springfox.documentation.annotations.ApiIgnore;

@Api(tags="页面初始化")
@RestController
public class IndexController extends BaseController {
	
	@Autowired
	private ISecurityService securityService;
	
	@Autowired
	private IMenuService menuService;
	
	@Autowired
	private ILauncherService launcherService;
	
	@Autowired
	private IDictService dictService;
	
	@Autowired
	private IAccountService accountService;
	
	@Autowired
	private PasswordEncoder passwordEncoder;
	
	@Autowired
	private ILauncherSettingService launcherSettingService;
	
	@Autowired
	private ISettingService settingService;
	
	@Autowired
	private RedisTemplate<String, String> redisTemplate;
	
	@ApiOperation("页面初始化信息")
	@GetMapping("/api/index/init")
	public Response<InitDTO> init(
			@ApiIgnore
			HttpSession session,
			@ApiParam(name="menuType", value="目录类型")
			@RequestParam(name="menuType", required=false)
			String menuType) throws IllegalAccessException, InstantiationException, InvocationTargetException, NoSuchMethodException {
		Account account = (Account) this.getLoginAccount();
		InitDTO result = new InitDTO();
		//是否未登录
		boolean isNotLogin = account == null;
		//session id
		result.setSessionId(session.getId());
		//未登录/登录的菜单
		result.setMenus(menuService.findByAccountId(isNotLogin ? Constants.NONE_ACCOUNT_ID : account.getId(), StringUtils.isBlank(menuType) ? MenuDTO.MENU_TYPE_SYSTEM : menuType));
		if(isNotLogin) {
			return Response.fail(HttpServletResponse.SC_UNAUTHORIZED, "用户未认证", result);
		}
		//权限
		result.setPermissions(securityService.getPermissions());
		//角色
		result.setRoles(securityService.getRoles());
		//自动启动
		result.setLaunchers(launcherService.findAllLauncherModules(account.getId()));
		//当前登录账号信息
		AccountDTO accountResult = AccountDTO.from(account);
		//去除密码信息
		accountResult.setPassword(null);
		result.setAccount(accountResult);
		//头像信息
		String avatar = settingService.findValueByKey(account.getId(), Constants.AVATAR_TYPE_KEY);
		if(StringUtils.isNotBlank(avatar)) {
			result.setAvatar(avatar);
		}
		//桌面类型
		String desktop = settingService.findValueByKey(account.getId(), Constants.DESKTOP_TYPE_KEY);
		if(StringUtils.isNotBlank(desktop)) {
			result.setDesktop(desktop);
		} else {
			result.setDesktop(Constants.DESKTOP_TYPE_DEFAULT_VALUE);
		}
		return Response.ok(result);
	}
	
	@ApiOperation("用户所有设置")
	@RequiresAuthentication
	@GetMapping("/api/index/settings")
	public Response<Map<String, String>> settings() {
		Account account = (Account) this.getLoginAccount();
		Map<String, String> settings = settingService.findSettings(account.getId());
		return Response.ok(settings);
	}
	
	@ApiOperation("用户单个设置")
	@RequiresAuthentication
	@GetMapping("/api/index/singlesetting")
	public Response<String> setting(
				@ApiParam(name="key", value="键", required=true)
				@RequestParam(name="key")
				String key) {
		Account account = (Account) this.getLoginAccount();
		String value = settingService.findValueByKey(account.getId(), key);
		return Response.ok(StringUtils.EMPTY, value);
	}
	
	@ApiOperation("用户变量设置")
	@RequiresAuthentication
	@PostMapping("/api/index/setting")
	public Response<String> setting(
			@ApiParam(name="key", value="键", required=true)
			@RequestParam(name="key")
			String key,
			@ApiParam(name="value", value="键值")
			@RequestParam(name="value", required=false)
			String value) {
		Account account = (Account) this.getLoginAccount();
		if(StringUtils.isBlank(value)) {
			settingService.deleteByKey(account.getId(), key);
		} else {
			settingService.save(account.getId(), key, value);
		}
		return Response.ok();
	}
	
	@ApiOperation("登录")
	@PostMapping("/api/login")
	public Response<Serializable> login(
			HttpServletRequest request,
			@RequestParam(name="username")
			@ApiParam(name="username", value="账户", required=true)
			String username,
			@RequestParam(name="password")
			@ApiParam(name="password", value="密码", required=true)
			String password,
			@RequestParam(name="host", required = false)
			@ApiParam(name="host", value="域名", required=false)
			String host,
			@RequestParam(name="rememberMe", required=false, defaultValue = "1")
			@ApiParam(name="rememberMe", value="记住我")
			boolean rememberMe) {
		UsernamePasswordToken token = new UsernamePasswordToken(username, password);
		token.setHost(host);
		token.setRememberMe(rememberMe);
		return login(token);
	}
	
	@ApiOperation("手机号码登录")
	@PostMapping("/api/mobile")
	public Response<Serializable> mobile(
			HttpServletRequest request,
			@ApiIgnore
			HttpSession session,
			@RequestParam(name="mobile")
			@ApiParam(name="mobile", value="手机号码", required=true)
			String username,
			@RequestParam(name="code")
			@ApiParam(name="code", value="验证码", required=true)
			String password,
			@RequestParam(name="host", required = false)
			@ApiParam(name="host", value="域名", required=false)
			String host,
			@RequestParam(name="rememberMe", required=false, defaultValue = "1")
			@ApiParam(name="rememberMe", value="记住我")
			boolean rememberMe) {
		String codeCacheKey = StringUtils.join(Constants.MOBILE_CODE_PREFIX, username);
		String code = redisTemplate.boundValueOps(codeCacheKey).get();
		if(StringUtils.isBlank(code)) {
			return Response.fail(1, "验证码错误，登录失败.");
		}
		UsernamePasswordToken token = new UsernameCodeToken(username, password, code);
		token.setHost(host);
		token.setRememberMe(rememberMe);
		Response<Serializable> result = login(token);
		if(result.isSuccess()) {
			redisTemplate.delete(codeCacheKey);
		}
		return result;
	}
	
	protected Response<Serializable> login(AuthenticationToken token) {
		Subject subject = SecurityUtils.getSubject();
		Response<Serializable> result = null;
		String username = (String) token.getPrincipal();
		Serializable sessionId = subject.getSession().getId();
		try {
			SecurityUtils.getSubject().login(token);
			log.info("账户{}登录成功.认证ID:{}", username, sessionId);
			result = Response.ok("登录成功.", sessionId);
		} catch(UnknownAccountException e) {
			result = Response.fail(0, "账户不存在，登录失败.");
			log.info("未知账户{}，登录失败.", username);
		} catch(LockedAccountException e) {
			result = Response.fail(2, "账户已被锁定，登录失败.");
			log.info("账户{}已被锁定，登录失败.", username);
		} catch(AuthenticationException e) {
			result = Response.fail(1, "验证码错误，登录失败.");
			log.info("账户{}验证码错误，登录失败.", username);
		}
		return result;
	}
	
	@ApiOperation("登出")
	@GetMapping("/api/logout")
	public Response<String> logout(@ApiIgnore HttpSession session) {
		SecurityUtils.getSubject().logout();
		return Response.ok();
	}

	@ApiOperation("拥有权限")
	@PostMapping("/api/perms")
	public Response<Map<String, Boolean>> perms(HttpServletRequest request) {
		Map<String, Boolean> result = new LinkedHashMap<>();
		Iterator<Entry<String, String[]>> it = request.getParameterMap().entrySet().iterator();
		Entry<String, String[]> entry = null;
		while(it.hasNext()) {
			entry = it.next();
			result.put(entry.getKey(), this.isPermitted(entry.getValue()[0]));
		}
		return Response.ok(result);
	}
	
	@ApiOperation("拥有角色")
	@PostMapping("/api/roles")
	public Response<Map<String, Boolean>> roles(HttpServletRequest request) {
		Map<String, Boolean> result = new LinkedHashMap<>();
		Iterator<Entry<String, String[]>> it = request.getParameterMap().entrySet().iterator();
		Entry<String, String[]> entry = null;
		while(it.hasNext()) {
			entry = it.next();
			result.put(entry.getKey(), this.hasRole(entry.getValue()[0]));
		}
		return Response.ok(result);
	}
	
	@ApiOperation("数据字典查询")
	@PostMapping("/api/dict")
	public Response<DictDTO> dict(
			@RequestParam(name="values")
			@ApiParam(name="values", value="字典值", required=true)
			String[] values, 
			@RequestParam(name="children", required=false, defaultValue="0")
			@ApiParam(name="children", value="是否查询下级字典")
			boolean children) {
		DictDTO result = null;
		if(values != null) {
			result = dictService.findByValues(values, children);
		}
		return result != null ? Response.ok(result) : Response.fail();
	}
	
	@ApiOperation("数据字典查询")
	@PostMapping("/api/dicts")
	public Response<Map<String, DictDTO>> dicts(@RequestBody DictParams params) {
		boolean children = params.isChildren();
		Map<String, String[]> values = params.getValues();
		Map<String, DictDTO> result = new LinkedHashMap<>();
		if(values != null) {
			Iterator<Entry<String, String[]>> it = values.entrySet().iterator();
			Entry<String, String[]> entry = null;
			while(it.hasNext()) {
				entry = it.next();
				result.put(entry.getKey(), dictService.findByValues(entry.getValue(), children));
			}
		}
		return Response.ok(result);
	}
	
	@ApiOperation("修改密码")
	@RequiresAuthentication
	@PostMapping("/api/index/password")
	public Response<String> password(
			@RequestParam(name="oldPassword")
			@ApiParam(name="oldPassword", value="旧密码", required=true)
			String oldPassword,
			@RequestParam(name="password")
			@ApiParam(name="password", value="新密码", required=true)
			String password) {
		Account account = (Account) this.getLoginAccount();
		Account old = accountService.findOne(account.getId());
		if(!this.passwordEncoder.matches(oldPassword, old.getPassword())) {
			return Response.fail(1);
		}
		old.setPassword(this.passwordEncoder.encode(password));
		accountService.save(old);
		//修改密码后退出登录
		SecurityUtils.getSubject().logout();
		return Response.ok();
	}
	
	@ApiOperation("自启动列表")
	@RequiresAuthentication
	@PostMapping("/api/index/launchers")
	public Response<List<LauncherDTO>> launchers() {
		Account account = (Account) this.getLoginAccount();
		List<Launcher> result = launcherService.findAllLaunchers(account.getId());
		return Response.ok(LauncherDTO.from(result));
	}
	
	@ApiOperation("自启动开关")
	@RequiresAuthentication
	@PostMapping("/api/index/launcher")
	public Response<String> launcher(
			@RequestParam(name="lid")
			@ApiParam(name="lid", value="自启动ID", required=true)
			String lid,
			@RequestParam(name="enable")
			@ApiParam(name="enable", value="开启/关闭", required=true)
			boolean enable) {
		Account account = (Account) this.getLoginAccount();
		Launcher launcher = launcherService.findOne(lid);
		if(launcher != null && launcher.isLauncherManual()) {
			LauncherSettingId id = new LauncherSettingId(account.getId(), lid);
			launcherSettingService.delete(id);
			LauncherSetting ls = new LauncherSetting(id);
			ls.setEnable(enable);
			ls.setNew(true);
			launcherSettingService.save(ls);
		}
		return Response.ok();
	}
	
}
